/*
 *  Generic checkpoint-restart
 *
 *
 *  This file is subject to the terms and conditions of the GNU General Public
 *  License.  See the file COPYING in the main directory of the Linux
 *  distribution for more details.
 */

#include <checkpoint.h>
#include <types.h>
#include <string.h>
#include <slab.h>
#include <sync.h>
#include <vfs.h>
#include <proc.h>
#include <file.h>
#include <unistd.h>
#include <iobuf.h>
#include <inode.h>
#include <stat.h>
#include <dirent.h>
#include <error.h>
#include <assert.h>
#include <clock.h>

void dummy(char* p, ...)
{
}


// initialize header info of chunk;
void init_chunk_header(struct ckpt_chunk_header *chead, int type)
{
	chead->chunk_magic = CKPT_CHUNK_MAGIC;
	chead->chunk_type = type;
	chead->chunk_len = 0;
}

int write_bits(struct ckpt_ctx *ctx, uintptr_t base, uint32_t size)
{
	ckprintf("DUMP::::%d\n", size);
//	sysfile_write(ctx->file_d, base, size);
	int sum;
	if (base == 0xaff00000)
	{
		base = 0xb0000000 - 8192;
		size = 8192;
	}
	int kkk;
	file_write(ctx->file_d, base, size, &kkk);
	ctx->total += size;

	return 0;
}

// write a single chunk
int write_chunk(struct ckpt_ctx *ctx, struct ckpt_chunk_header *chead, void* obj)
{
	int ret;
	if (ret = write_bits(ctx, chead, sizeof(struct ckpt_chunk_header)))
		return ret;
	return write_bits(ctx, obj, chead->chunk_len);
}

void check_chunk_header(struct ckpt_chunk_header *chead, int type)
{
	assert(chead->chunk_magic == CKPT_CHUNK_MAGIC);
	assert(chead->chunk_type == type);
}

int read_bits(struct ckpt_ctx *ctx, uintptr_t base, uint32_t size)
{
	ckprintf("Read::::%08x %d\n", base, size);
//	sysfile_write(ctx->file_d, base, size);
	if (base == 0xaff00000)
	{
		base = 0xb0000000 - 8192;

		size = 8192;
	}

	int kkk;
	file_read(ctx->file_d, base, size, &kkk);
	ctx->total += size;

	return 0;
}

// write a single chunk
int read_chunk(struct ckpt_ctx *ctx, int type, void* obj)
{
	int ret;
	struct ckpt_chunk_header chead;
	if (ret = read_bits(ctx, &chead, sizeof(struct ckpt_chunk_header)))
		return ret;
	check_chunk_header(&chead, type);
//	obj = kmalloc(chead->chunk_len);
	return read_bits(ctx, obj, chead.chunk_len);
}

void switchtopgdir(struct mm_struct *mm)
{
	current->mm = mm;
	set_pgdir(current, mm->pgdir);
	mp_set_mm_pagetable(mm);
}

void switchtofs(struct fs_struct *fs)
{
	current->fs_struct = fs;
}

/**
 * walk_task_subtree: iterate through a task's descendants
 * @root: subtree root task
 * @func: callback invoked on each task
 * @data: pointer passed to the callback
 *
 * The function will start with @root, and iterate through all the
 * descendants, including threads, in a DFS manner. Children of a task
 * are traversed before proceeding to the next thread of that task,
 * and threads of a task are traversed before proceeding to the next
 * sibling of that task. (Threads of the root task are included, but
 * siblings of the root task are skipped).
 *
 * For each task, the callback @func will be called providing the task
 * pointer and the @data. The callback is invoked while holding the
 * tasklist_lock for reading. If the callback fails it should return a
 * negative error, and the traversal ends. If the callback succeeds,
 * it returns a non-negative number, and these values are summed.
 *
 * On success, walk_task_subtree() returns the total summed. On
 * failure, it returns a negative value.
 */
int walk_task_subtree(struct proc_struct *root,
		      int (*func)(struct proc_struct *, void *),
		      void *data)
{
	struct proc_struct *task = root;
	struct proc_struct *parent = NULL;
	int total = 0;
	int ret;

	while (1) {

		ckprintf("Ckpt : walk task : %08x\n", task);

		/* invoke callback on this task */
		ret = func(task, data);
		if (ret < 0)
			break;

		total += ret;

		/* if has children - proceed with child */
		if (task->cptr) { //children)) {
			parent = task;
			task = task->cptr;
			continue;
		}

		/* by definition, skip siblings of root */
		while (task != root) {
			/* if not last thread - proceed with thread */
			// U11 : currently set the oi
//			task = next_thread(task);
//			if (!thread_group_leader(task))
//				break;

			/* if has sibling - proceed with sibling */
			if (task->optr) {
				task = task->optr;
				break;
			}

			/* else, trace back to parent and proceed */
			task = parent;
			parent = parent->parent;
		}

		/* if we arrive at root again -- done */
		if (task == root) {
//			if (thread_group_leader(task)) TODO : ???
			break;
		}
	}

	ckprintf("total %d ret %d\n", total, ret);
	return (ret < 0 ? ret : total);
}

static void ckpt_ctx_free(struct ckpt_ctx *ctx)
{
	// /* per task status debugging only during restart */
	// if (ctx->kflags & CKPT_CTX_RESTART)
		// restore_debug_free(ctx);

	// if (ctx->deferqueue)
		// deferqueue_destroy(ctx->deferqueue);

	// if (ctx->files_deferq)
		// deferqueue_destroy(ctx->files_deferq);

	// if (ctx->file)
		// fput(ctx->file);
	// if (ctx->logfile)
		// fput(ctx->logfile);

	// ckpt_obj_hash_free(ctx);
	// path_put(&ctx->root_fs_path);
	// ckpt_pgarr_free(ctx);

	// if (ctx->tasks_arr)
		// task_arr_free(ctx);

	// if (ctx->root_nsproxy)
		// put_nsproxy(ctx->root_nsproxy);
	// if (ctx->root_task)
		// put_task_struct(ctx->root_task);
	// if (ctx->root_freezer)
		// put_task_struct(ctx->root_freezer);

	// free_pages(ctx->scratch_page_page_struct, 1);

	kfree(ctx);
}

static struct ckpt_ctx *ckpt_ctx_alloc(int fd, unsigned long uflags, unsigned long kflags, int logfd)
{
	struct ckpt_ctx *ctx;
	int err;

	// alloc the struct space
	ctx = kmalloc(sizeof(struct ckpt_ctx));
	if (!ctx)
		return NULL;

	ckprintf("Ckpt: malloc ckpt_ctx succeed\n");

	ctx->uflags = uflags;
	ctx->kflags = kflags;
	ctx->ktime_begin = (int)ticks;// sys_gettime();

	ckprintf("Ckpt: system ticks %d\n", ctx->ktime_begin);

	atomic_set(&(ctx->refcount), 0);
//	list_init(&(ctx->pgarr_list));
//	list_init(&(ctx->pgarr_pool));

	// TODO: init_waitqueue_head(&ctx->waitq);
	// TODO: init_waitqueue_head(&ctx->ghostq);
	// TODO: init_completion(&ctx->complete);

	// TODO: init_completion(&ctx->errno_sync);

	// TODO: mutex_init(&ctx->msg_mutex);

	// TODO: INIT_LIST_HEAD(&ctx->listen_sockets);

	ckprintf("Ckpt: test record file...\n");

	// U11 : get file descriptor
	err = -E_NOENT;
	ctx->file_d = fd;

	// for restart
	if (kflags & CKPT_CTX_RESTART)
	{
		if (!file_testfd(fd, 1, 0))
				goto err;
	}
	else	// for checkpoint
	{
		if (!file_testfd(fd, 0, 1))
			goto err;
	}

	ckprintf("Ckpt: file test Passed...\n");

	if (logfd == -1)
		goto nolog;
	ctx->logfile_d = logfd;
	if (!file_testfd(logfd, 0, 1))
		goto err;

	ckprintf("Ckpt: logfile test Passed...\n");

 nolog:

	ckprintf("Ckpt: No log begin...\n");

	err = -E_NO_MEM;
	//if (ckpt_obj_hash_alloc(ctx) < 0)
	//	goto err;
	// TODO :
	// TODO: ctx->deferqueue = deferqueue_create();
	// TODO: if (!ctx->deferqueue)
	// TODO: 	goto err;

	// TODO: ctx->files_deferq = deferqueue_create();
	// TODO: if (!ctx->files_deferq)
	// TODO: 	goto err;

	// U11 : alloc scratch page
	// ctx->scratch_page = (void *) __get_free_page(GFP_KERNEL);
//	ctx->scratch_page_page_struct = alloc_page();
//	if (!ctx->scratch_page_page_struct)
//		goto err;
//	ctx->scratch_page = page2kva(ctx->scratch_page_page_struct);

	atomic_inc(&(ctx->refcount));
	return ctx;
 err:
 	ckprintf("Ckpt: error of ctx-alloc\n");
	ckpt_ctx_free(ctx);
	return NULL;
}

void ckpt_ctx_put(struct ckpt_ctx *ctx)
{
	if (ctx && atomic_dec_test_zero(&(ctx->refcount)))
		ckpt_ctx_free(ctx);
}

/**
 * do_sys_checkpoint - checkpoint
 * @pid: pid of the current
 * @fd: file to which dump the checkpoint image
 * @flags: checkpoint operation flags
 * @logfd: fd to which to dump debug and error messages
 *
 * Returns positive identifier on success, 0 when returning from restart
 * or negative value on error
 */
int do_sys_checkpoint(pid_t pid, int fd, uint32_t flags, int logfd)
{
	struct ckpt_ctx *ctx;
	int ret;

	ckprintf("Ckpt: preperation begin...\n");

	if (pid == 0)	// checkpoint the current process
	{
		pid = current->pid;
		ckprintf("Ckpt: checkpoint current prcess, pid = %d\n", pid);
	}

	ckprintf("Ckpt: begin to allocate ckpt_cxx ...\n");
	ctx = ckpt_ctx_alloc(fd, flags, CKPT_CTX_CHECKPOINT, logfd);
	if (!ctx)
		return -E_NO_MEM;
	ckprintf("Ckpt: end of aloocate ckpt_cxx....\n");
	
	char *rew = "Helloworld!";

	//write_bits(ctx, rew, 5);


	// U11 : real checkpoint process
	ckprintf("Warning: begin real checkpoint...\n");
	ret = do_checkpoint(ctx, pid);

	if (!ret)
		ret = ctx->crid;
	
	// close file
	ckprintf("Checkpoint : file iD = %d\n", ctx->file_d);
	if (flags & CHECKPOINT_FILECLOSE)
		sysfile_close(ctx->file_d);

	// dispose context
	ckpt_ctx_put(ctx);

	// TODO: rempppro to 1
	return 1;
}

/**
 * do_sys_restart - restart a container
 * @pid: pid of task root (in coordinator's namespace), or 0
 * @fd: file from which read the checkpoint image
 * @flags: restart operation flags
 * @logfd: fd to which to dump debug and error messages
 *
 * Returns negative value on error, or otherwise returns in the realm
 * of the original checkpoint
 */
int do_sys_restart(pid_t pid, int fd, uint32_t flags, int logfd)
{
	struct ckpt_ctx *ctx = NULL;
	int ret;

	ckprintf("Restatt SYS \n");


//	if (pid)
		ctx = ckpt_ctx_alloc(fd, flags, CKPT_CTX_RESTART, logfd);

	ret = do_restart(ctx, pid, flags);


	ckpt_ctx_put(ctx);
	return ret;
}
